package model;

import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import observer.Observable;
import observer.Observer;

/**
 * Classe representant le modele de l'application
 * @author Lefevre, Thoue
 */
public class Model implements Observable,Serializable{
    private User user;
    private User userChatActif;
    private Manager manager;
    private ArrayList<Observer> listObserver = new ArrayList<Observer>();
    private Vector<Call> listCall;
    private Vector<CallActive> listCallActive;
    private Vector<User> listContact;
    private int seuilSilence;
    private int actualVoiceLevel;
    private int nbPaquetSon;
    private float sampleRate;
    private int sampleSizeInBits;
    private int channels;
    private boolean signed;
    private boolean bigEndian;

    /**
     * Constructeur par defaut
     */
    public Model() {
        listCall = new Vector<Call>();
        listCallActive = new Vector<CallActive>();
        listContact = new Vector<User>();
        this.manager = new Manager(this);
        //audioformat par dÃ©faut
        sampleRate =16000;
        sampleSizeInBits=16;
        channels=1;
        signed=true;
        bigEndian=true;
        seuilSilence=5;        
    }
    /**
     * Getter user
     * @return les information de l'utilisateur courrant
     */
    public User getCurrentUser() {
        return this.user;
    }
    /**
     * Setter user
     * @param user utilisateur courrant
     */
    public void setCurrentUser(User user) {
        this.user=user;
        notifyObserver(this);
    }    
    /**
     * Getter manager
     * @return le manager des threads
     */
    public Manager getManager() {
        return manager;
    }
    /**
     * Getter listCall
     * @return la liste des demandes
     */
    public Vector<Call> getListCall() {
        return listCall;
    }
    /**
     * Getter listCallActive
     * @return la liste des conversations actives
     */
    public Vector<CallActive> getListCallActive() {
        return listCallActive;
    }
    /**
     * Getter listContact
     * @return la liste des contacts
     */
    public Vector<User> getListContact() {
        return listContact;
    }    
    /**
     * Getter seuilSilence
     * @return le seuil de silence en dessous du quel le son n'est pas envoye
     */
    public int getSeuilSilence() {
        return seuilSilence;
    }
    /**
     * Setter seuilSilence
     * @param seuilSilence seuil de silence en dessous du quel le son n'est pas envoye
     */
    public void setSeuilSilence(int seuilSilence) {
        this.seuilSilence = seuilSilence;
        notifyObserver(this);
    }
    /**
     * Getter actualVoiceLevel
     * @return le niveau actuel de la voix
     */
    public int getActualVoiceLevel() {
        return actualVoiceLevel;
    }
    /**
     * Setter actualVoiceLevel ( appeler 1 fois sur 10 poue eviter une mise a jour trop courante de la vue)
     * @param actualVoiceLevel le niveau actuel de la voix
     */
    public void setActualVoiceLevel(int actualVoiceLevel) {
        nbPaquetSon++;
        if(nbPaquetSon%10==0){
        this.actualVoiceLevel = actualVoiceLevel;
         notifyObserver(this);
        }
    }
    /**
     * Getter channel
     * @return le canal du son prefere par l'utilisateur
     */
    public int getChannels() {
        return channels;
    }
    /**
     * Getter sampleRate
     * @return l'echantillonage du son prefere par l'utilisateur
     */
    public float getSampleRate() {
        return sampleRate;
    }
    /**
     * Getter sampleSizeInBits
     * @return la taille d'un echantillon du son prefere par l'utilisateur
     */
    public int getSampleSizeInBits() {
        return sampleSizeInBits;
    }
    /**
     * Observateur bigEndian
     * @return l'ordre des bits du son prefere par l'utilisateur
     */
    public boolean isBigEndian() {
        return bigEndian;
    }
    /**
     * Observateur signed
     * @return vrai si le son prefere par l'utilisateur est signe
     */
    public boolean isSigned() {
        return signed;
    }
    /**
     * Getter userChatActif
     * @return les informations de l'utilisateur destinaire du chat
     */
    public User getUserChatActif() {
        return userChatActif;
    }
    /**
     * Setter userChatActif
     * @param userChatActif les informations de l'utilisateur destinaire du chat
     */
    public void setUserChatActif(User userChatActif) {
        this.userChatActif = userChatActif;
        notifyObserver(this);
    }
    /**
     * Recupere l'historique du chat actif
     * @return la chaine de caratere correspondant au chat actif
     */
    public String getHistoriqueChatActif() {
        return manager.getHistorique(userChatActif.getId());
    }
    /**
     * Permet de couper le son d'une conversation
     * @param user utilisateur concerner
     * @param mute couper/remettre le son
     */
    public void setMute(User user, boolean mute) {
        for (CallActive call : listCallActive) {
            if (call.getUser().getIp().equals(user.getIp()) && call.getUser().getPort() == user.getPort()) {
                call.setMute(mute);
            }
        }
        manager.setMute(user.getId(),mute);
        notifyObserver(this);
    }
    /**
     * Permet de mettre en pause une conversation
     * @param user utilisateur concerner
     * @param pause mettre en pause/reprendre une conversation
     */
    public void miseEnPause(User user, boolean pause) {
        for (CallActive call : listCallActive) {
            if (call.getUser().getIp().equals(user.getIp()) && call.getUser().getPort() == user.getPort()) {
                call.setPause(pause);
            }
        }
        notifyObserver(this);
    }
    /**
     * Permet de mettre fin a une conversation
     * @param user utilisateur concerner
     */
    public void Raccrocher(User user) {
        CallActive callASuppr = null;
        for (Iterator<CallActive> it = listCallActive.iterator(); it.hasNext();) {
            CallActive call = it.next();
            if (call.getUser().getId() == user.getId()) {
                callASuppr = call;
            }
        }
        if (callASuppr != null) {
            listCallActive.remove(callASuppr);
        }
        manager.racrocher(user.getId());
        notifyObserver(this);
    }
    /**
     * Avertis la vue qu'un appel est termine ou refuse
     */
    public void avertirFinAppel(String ip, int port){
        
        //retirer de la liste des demande si present
        Call callASuppr = null;
        for (Iterator<Call> it = listCall.iterator(); it.hasNext();) {
            Call call = it.next();
            if (call.getUser().getId() == user.getId()) {
                callASuppr = call;
            }
        }
        if (callASuppr != null) {
            listCall.remove(callASuppr);
        }
         //retirer de la liste des conversations en cours si present
        CallActive callactiveASuppr = null;
        for (Iterator<CallActive> it = listCallActive.iterator(); it.hasNext();) {
            CallActive call = it.next();
            if (call.getUser().getIp().equals(ip) && call.getUser().getPort() == port) {
                callactiveASuppr = call;
            }
        }
        if (callactiveASuppr != null) {
            listCallActive.remove(callactiveASuppr);
        }
         //retirer du chat actif si present
        if(userChatActif!=null){
            if (userChatActif.getIp().equals(ip) && userChatActif.getPort() == port) {
                    userChatActif = null;
            }
        }
        
        notifyObserver(this);
    }
    /**
     * Permet de mettre en pause une conversation et d'avertir l'utilisateur
     * @param user utilisateur concerner
     * @param pause mettre en pause/reprendre une conversation
     */
    public void setPause(User user, boolean pause) {
        for (CallActive call : listCallActive) {
            if (call.getUser().getIp().equals(user.getIp()) && call.getUser().getPort() == user.getPort()) {
                call.setPause(pause);
            }
        }
        manager.setPause(user.getId(),pause);
        notifyObserver(this);
    }
    /**
     * Ajoute un utilisateur a la liste des demandes
     * @param user utilisateur concerner
     */
    public void addCall(User user) {
        boolean isExist = false;
        for (Iterator<Call> it = listCall.iterator(); it.hasNext();) {
            Call call = it.next();
            if (call.getUser().getId() == user.getId()) {
                isExist = true;
            }
        }
        if (isExist == false) {
            listCall.add(new Call(user));
            notifyObserver(this);
        }
    }
     /**
     * Ajoute un utilisateur a la liste des demandes
     * @param user utilisateur concerner
     * @param status status de la demande (attente/confirmer)
     */
    public void addCall(User user, EStatusCall status) {
        boolean isExist = false;
        for (Iterator<Call> it = listCall.iterator(); it.hasNext();) {
            Call call = it.next();
            if (call.getUser().getId() == user.getId()) {
                isExist = true;
            }
        }
        if (isExist == false) {
            listCall.add(new Call(user,status));
            notifyObserver(this);
        }
    }
    /**
     * Ajoute un utilisateur a la liste des conversations actives
     * @param user utilisateur concerner
     */
    public void addCallActive(User user) {
        listCallActive.add(new CallActive(user));
        
        Call callASuppr = null;
        for (Iterator<Call> it = listCall.iterator(); it.hasNext();) {
            Call call = it.next();
            if (call.getUser().getId() == user.getId()) {
                callASuppr = call;
            }
        }
        if (callASuppr != null) {
         listCall.remove(callASuppr);
        }
        notifyObserver(this);
        
    }
    /**
     * Supprime un utilisateur a la liste des conversations actives
     * @param id identifant de l'utilisateur concerne
     */
    public void deleteCallActive(int id) {
        CallActive callASuppr = null;
        for (Iterator<CallActive> it = listCallActive.iterator(); it.hasNext();) {
            CallActive call = it.next();
            if (call.getUser().getId() == id) {
                callASuppr = call;
            }
        }
        if (callASuppr != null) {
            listCallActive.remove(callASuppr);
        }
        notifyObserver(this);

    }
    /**
     * Refuse une demande de conversation
     * @param user utilisateur concerner
     */
    public void Refuser(User user) {
        Call callASuppr = null;
        for (Iterator<Call> it = listCall.iterator(); it.hasNext();) {
            Call call = it.next();
            if (call.getUser().getId() == user.getId()) {
                callASuppr = call;
            }
        }
        if (callASuppr != null) {
         listCall.remove(callASuppr);
         manager.refuser(user.getId());
        }
        
        notifyObserver(this);
    }
    /**
     * Accepte une demande de conversation
     * @param user utilisateur concerner
     */
    public void accepter(User user){
        manager.accepter(user.getId());
        notifyObserver(this);
    }
    /**
     * Confirme une acceptation de conversation
     * @param user utilisateur concerner
     */
    public void Confirmer(User user) {
        manager.confirmer(user.getId());
        Call callASuppr = null;
        for (Iterator<Call> it = listCall.iterator(); it.hasNext();) {
            Call call = it.next();
            if (call.getUser().getId() == user.getId()) {
                callASuppr = call;
                listCallActive.add(new CallActive(user));
            }
        }
        if (callASuppr != null) {
            listCall.remove(callASuppr);
        }
        notifyObserver(this);
    }
    /**
     * Initialise les parametres prefere de l'utilisateur
     * @param sampleRate echantillonnage du son
     * @param sampleSizeInBits taille d'un echantillon du son
     * @param channels cannal du son
     * @param signed son signe ou non
     * @param bigEndian ordre des bits du son
     */
    public void setSoundParamater(float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian) {
        this.sampleRate = sampleRate;
        this.sampleSizeInBits = sampleSizeInBits;
        this.channels = channels;
        this.signed = signed;
        this.bigEndian = bigEndian;
        notifyObserver(this);
    }
    /**
     * Envoie un message ecrit a l'utilisateur userChatActif
     * @param message message ecrit
     */
    public void envoyerMessage(String message) {
        if(userChatActif !=null){
            manager.envoyerMessage(message, userChatActif.getId());
            notifyObserver(this);
        }
    }
    /**
     * Demarre le serveur d'ecoute tcp
     */
    public void ecouterDemandeDeConversation(){
        manager.ecoute();
    }
    /**
     * Appel un utilisateur
     * @param ip adresse ip d'ecoute de l'utilisateur
     * @param port port d'ecoute de l'utilisateur
     */
    public void appeler(String ip, int port){
        manager.appelerNouvellePersonne(ip, port);
    }
    /**
     * Ajoute un contact dans la liste
     * @param user utilisateur concerner
     */
    public void ajouterContact(User user){
        listContact.add(user);
        notifyObserver(this);
    }
    /**
     * Supprimer un contact dans la liste
     * @param user utilisateur concerner
     */
    public void supprimerContact(User user){
        listContact.remove(user);
        notifyObserver(this);
    }
    /**
     * Sauvegarde la liste des contacts
     */    
    public void saveContact(){
        try{
           FileOutputStream fichier = new FileOutputStream("Contact.ser");
           ObjectOutputStream oos = new ObjectOutputStream(fichier);
           oos.writeObject(listContact);
           oos.flush();
           oos.close();
       }
       catch (java.io.IOException e) {
        Logger.getLogger(Model.class.getName()).log(Level.INFO, "La liste des contact n'est pas sauvegarder.");
       }
    }
    /**
     * Charge la liste des contacts
     */
    public void loadContact(){
        try {
            FileInputStream fichier = new FileInputStream("Contact.ser");
            ObjectInputStream ois = new ObjectInputStream(fichier);
            listContact = (Vector<User>) ois.readObject();
            }
            catch (java.io.IOException e) {
            Logger.getLogger(Model.class.getName()).log(Level.INFO, "La liste des contact n'est pas charge.");
            }
            catch (ClassNotFoundException e) {
            Logger.getLogger(Model.class.getName()).log(Level.INFO, "La liste des contact n'est pas charge.");
        }
    }
    
    
    /**
     * Abonne un observateur au model
     * @param obs 
     */
    @Override
    public void addObserver(Observer obs) {
        this.listObserver.add(obs);
    }
    /**
     * Avertis ses abonnees d'un changement
     * @param amodel 
     */
    @Override
    public void notifyObserver(Model amodel) {
        //element a notifier
        for (Observer obs : listObserver) {
            obs.update(amodel);
        }
    }
    /**
     * retire un abonne de la liste des observateurs
     */
    @Override
    public void removeObserver() {
        listObserver = new ArrayList<Observer>();
    }
    
}